﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;

//Copyright (C) 2010 Florian Kolb

//This program is free software; you can redistribute it and/or modify it under the terms 
//of the GNU General Public License as published by the Free Software Foundation; either 
//version 2 of the License, or (at your option) any later version.

//This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
//without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
//See the GNU General Public License for more details.

//You should have received a copy of the GNU General Public License along with this program; 
//if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA 

namespace Spider_Compiler
{
    public class Parser
    {
        #region Private Fields
        private const string commandsElement = "commands";
        private const string commandElement = "command";
        private const string expressionSplitter = ">>";
        private const string paramSplitter = ";";
        private const string varId = "var";
        private const string varDeclarationId = "declaration";
        private const string paramDeclaration = "param";
        private const string pathConfigRootElement = "spider";
        private const string typeDefinitions = "typeDefinitions";
        private const string comment = "#";
        private Dictionary<string, object> varDict = new Dictionary<string, object>();
        #endregion

        #region Public Fields
        public delegate void ParseError(string error);
        public event ParseError Error;
        public int ErrorCount { private set; get; }
        #endregion

        public Parser()
        {
            Error += ParserError;
        }

        private void ParserError(string error)
        {
            ErrorCount++;
        }

        private XmlDocument GetPathConfig()
        {
            XmlDocument pathConfig = new XmlDocument();
            pathConfig.Load(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "path.xml"));
            return pathConfig;
        }

        private string GetVarDeclaration()
        {
            XmlDocument pathConfig = GetPathConfig();

            string _var = pathConfig.SelectSingleNode(string.Format("{0}//*[@id='{1}']", pathConfigRootElement, varId)).Name;
            string _varDeclaration = pathConfig.SelectSingleNode(string.Format("{0}//*[@id='{1}']/*[1][@id='{2}']", pathConfigRootElement, varId, varDeclarationId)).Name;

            return string.Format("{0}/{1}", _var, _varDeclaration);
        }

        private bool ExpressionIsVar(string expression)
        {
            return expression.StartsWith(string.Format("{0} {1} ", GetVarDeclaration(), expressionSplitter));
        }

        public List<CompilerInstruction> ParseExpressions(string[] expressions)
        {
            List<CompilerInstruction> instructions = new List<CompilerInstruction>();

            int i = 1;

            foreach (string expression in expressions)
            {
                //Only parse if the command is not a comment
                if (!expression.StartsWith(comment))
                {
                    instructions.Add(ParseExpression(expression, i));
                }
                i++;
            }

            return instructions;
        }

        public List<CompilerInstruction> ParseExpressions(List<string> expressions)
        {
            return ParseExpressions(expressions.ToArray());
        }

        private CompilerInstruction ParseExpression(string expression, int line)
        {
            XmlDocument pathConfig = GetPathConfig();
            string path = expression.Split(new string[] { expressionSplitter }, StringSplitOptions.RemoveEmptyEntries)[0].Replace(" ", string.Empty);
            string exp = string.Empty;

            try
            {
                exp = expression.Split(new string[] { expressionSplitter }, StringSplitOptions.RemoveEmptyEntries)[1];
            }
            catch
            {
            }

            List<string> expList = new List<string>();

            //If the command contains more parameter
            if (exp.Contains(paramSplitter))
            {
                expList.AddRange(exp.Split(new string[] { paramSplitter }, StringSplitOptions.None).ToList());
            }

            string currentParent = string.Empty;
            List<string> pathList = new List<string>();

            XmlNodeList paths = pathConfig.SelectNodes(
            string.Format("{0}//*[name(.) != '{1}' and name(parent::node()) != '{2}' and name(.) != '{3}' and " +
            "name(.) != '{4}' and name(parent::node()) != '{5}']", pathConfigRootElement, paramDeclaration, pathConfigRootElement,
            commandsElement, commandElement, typeDefinitions));

            foreach (XmlNode path_ in paths)
            {
                pathList.Add(string.Format("{0}/{1}", path_.ParentNode.Name, path_.Name)); 
            }

            CompilerInstruction instruction = new CompilerInstruction();

            if (pathList.Contains(path))
            {
                foreach (XmlNode node in pathConfig.SelectNodes(string.Format("{0}//*", pathConfigRootElement)))
                {
                    if (path == string.Format("{0}/{1}", node.ParentNode.Name, node.Name))
                    {
                        try
                        {
                            instruction.Command = node.SelectSingleNode(string.Format("{0}/{1}", commandsElement, commandElement)).InnerText;
                        }
                        catch
                        {
                        }

                        instruction.Parameter = new Dictionary<string, object>();

                        if (node.Attributes.Count > 0 && node.Attributes["id"] != null)
                        {
                            instruction.Id = node.Attributes["id"].Value;
                        }
                        else
                        {
                            instruction.Id = string.Empty;
                        }

                        int i = 0;

                        if (node.SelectNodes(paramDeclaration).Count > 0)
                        {
                            foreach (XmlNode param in node.SelectNodes(paramDeclaration))
                            {
                                if (expList.Count > 0)
                                {
                                    instruction.Parameter.Add(param.Attributes["name"].Value, expList[i].Remove(0, 1));
                                }
                                else
                                {
                                    instruction.Parameter.Add(param.Attributes["name"].Value, exp.Remove(0, 1));
                                }
                                i++;
                            }
                        }
                    }
                }

                if (ExpressionIsVar(expression))
                {
                    
                }
            }
            else
            {
                Error(string.Format("Line {0}: The command '{1}' does not exist!", line, path));
            }

            return instruction;
        }
    }
}
